package ms.jasim.framework;

import java.awt.BasicStroke;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import ms.jasim.framework.EventSimulator.SimulatedTreeNode;
import ms.jasim.model.Evaluator;
import ms.jasim.model.JasimModel;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.labels.XYItemLabelGenerator;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.general.Dataset;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

public class LineChartCreator implements IChartCreator {

	private JFreeChart chart;
	private MyXYItemLabelGenerator chartItemLabel;
	private Dataset dataset;
	private Iterable<SimulatedTreeNode> nodes;
	private JasimModel model;
	private Evaluator evaluatorInfo;

	public static class XYItemLabelItem {
		private int series, item;
		private String label;

		public XYItemLabelItem(int seriesIdx, int i, String string) {
			series = seriesIdx;
			item = i;
			label = string;
		}

		public int getSeries() {
			return series;
		}

		public void setSeries(int series) {
			this.series = series;
		}

		public int getItem() {
			return item;
		}

		public void setItem(int item) {
			this.item = item;
		}

		public String getLabel() {
			return label;
		}

		public void setLabel(String label) {
			this.label = label;
		}
	}

	public static class MyXYItemLabelGenerator implements XYItemLabelGenerator {

		public final List<XYItemLabelItem> items = new ArrayList<XYItemLabelItem>();

		@Override
		public String generateLabel(XYDataset arg0, int arg1, int arg2) {
			for (XYItemLabelItem item : items)
				if (item.series == arg1 && item.item == arg2)
					return item.label;
			return null;
		}
	}
	
	@Override
	public JFreeChart createChart(Iterable<SimulatedTreeNode> nodes,
			Evaluator evaluator, String evalComponent, IJasimAppContext context) {
		this.evaluatorInfo = evaluator;
		this.nodes = nodes;
		//this.context = context;
		
		if (chart == null)
			createChart();
		populateDataset(evalComponent, dataset, chart);
		return this.chart;
	}
	
	private JFreeChart createChart() {
		dataset = new XYSeriesCollection();
		chart = ChartFactory.createXYLineChart("Solution Simulation", "Simulation Time", 
				"Chart Title", 
				(XYDataset) dataset, PlotOrientation.VERTICAL, true, true, false);
		
		chartItemLabel = new MyXYItemLabelGenerator();

		XYPlot localXYPlot = (XYPlot) chart.getPlot();
		localXYPlot.setDomainPannable(true);
		localXYPlot.setRangePannable(true);
		XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) localXYPlot.getRenderer();
		renderer.setBaseShapesVisible(true);
		renderer.setBaseShapesFilled(true);
		renderer.setBaseItemLabelsVisible(true);
		renderer.setBaseItemLabelGenerator(chartItemLabel);
		
		return chart;
	}

	private void populateDataset(String component, Dataset dataset, JFreeChart  chart) {
		XYSeriesCollection ds = (XYSeriesCollection)dataset;
		ArrayList<Float> values = new ArrayList<Float>();
		
		ds.removeAllSeries();
		chartItemLabel.items.clear();
		XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) ((XYPlot) chart.getPlot()).getRenderer();
		
		Queue<SimulatedTreeNode> queue = new LinkedList<SimulatedTreeNode>();
		
		for (SimulatedTreeNode root_node: this.nodes) {
			queue.add(root_node);
			
			values.clear();
			int currentDepth = root_node.getDepth();
			SimulatedTreeNode lastVisitNode = null;

			XYSeries series = new XYSeries(root_node.getLabel(), true, true);
			int seriesIdx = ds.getSeriesCount();
			ds.addSeries(series);
			renderer.setSeriesStroke(seriesIdx, new BasicStroke(2));

			while (!queue.isEmpty()) {
				SimulatedTreeNode node = queue.poll();
				
				if (this.model == null)
					this.model = node.getSimulator().getModel();
				
				if (node.getDepth() != currentDepth) {
					if (values.size() > 0 && lastVisitNode != null) {
						series.add(lastVisitNode.getSimulator().getInitialTime(), getEvalMetric(0, values));
					}
					currentDepth = node.getDepth();
					values.clear();
				}
				float cost = node.getEvaluatorResult(evaluatorInfo, false).getValue(component);
				values.add(cost);
				if (!node.isLeaf())
					for (SimulatedTreeNode c: node.getChildren())
						queue.add(c);
				
				lastVisitNode = node;
			}

			if (values.size() > 0 && lastVisitNode != null) {
				series.add(lastVisitNode.getSimulator().getInitialTime(), getEvalMetric(0, values));
				if (lastVisitNode.getSimulator().isCompleted()) {
					int endTime = lastVisitNode.getSimulator().getEndTime();
					series.add(endTime, getEvalMetric(0, values));
					chartItemLabel.items.add(new XYItemLabelItem(seriesIdx, endTime, "stuck"));
				}
			}

		}
	}

	private float getEvalMetric(int function, List<Float> values) {
		float result = Float.NaN;
		switch (function) {
		case 0: // minimum
			result = Float.MAX_VALUE;
			for (float f : values)
				if (f < result)
					result = f;
			break;
		case 1: // max
			result = Float.MIN_VALUE;
			for (float f : values)
				if (f > result)
					result = f;
			break;
		case 2: // average
			result = 0;
			for (float f : values)
				result += f;
			result = result / values.size();
		}
		return result;
	}
}
